#include <arpa/inet.h>   // 网络地址转换
#include <errno.h>       // 错误号定义
#include <fcntl.h>       // 文件控制操作
#include <netinet/in.h>  // 互联网地址族
#include <signal.h>      // 信号处理
#include <stdio.h>       // 标准输入输出
#include <stdlib.h>      // 标准库函数
#include <string.h>      // 字符串操作
#include <sys/socket.h>  // 套接字接口
#include <sys/types.h>   // 系统数据类型
#include <unistd.h>      // POSIX系统调用
#include <time.h>
#include <sys/time.h>
#include <sys/stat.h>

// 常量定义
#define VERSION 23     // 服务器版本
#define BUFSIZE 8096   // 缓冲区大小
#define ERROR 42       // 错误日志类型
#define LOG 44         // 普通日志类型
#define FORBIDDEN 403  // 禁止访问状态码
#define NOTFOUND 404   // 文件未找到状态码

// 支持的文件扩展名与MIME类型映射
struct {
    char *ext;       // 文件扩展名
    char *filetype;  // 对应MIME类型
} extensions[] = {
    {"gif", "image/gif"},    // GIF图片
    {"jpg", "image/jpg"},    // JPEG图片
    {"jpeg", "image/jpeg"},  // JPEG图片
    {"png", "image/png"},    // PNG图片
    {"ico", "image/ico"},    // ICO图标
    {"zip", "image/zip"},    // ZIP压缩包（原代码MIME类型可能有误）
    {"gz", "image/gz"},      // GZ压缩包
    {"tar", "image/tar"},    // TAR归档
    {"htm", "text/html"},    // HTML文件
    {"html", "text/html"},   // HTML文件
    {0, 0}                   // 结束标记
};

/* 日志记录函数
 * 参数：
 *   type - 日志类型（ERROR/LOG/FORBIDDEN/NOTFOUND）
 *   s1 - 主要描述信息
 *   s2 - 补充信息
 *   socket_fd - 关联的套接字描述符
 */
void logger(int type, char *s1, char *s2, int socket_fd) {
    int fd;
    char logbuffer[BUFSIZE * 2];  // 日志缓冲区

    time_t now;
    struct tm *tm_info;
    char timestamp[32];

    time(&now);
    tm_info = localtime(&now);
    strftime(timestamp, sizeof(timestamp), "[%Y-%m-%d %H:%M:%S]", tm_info);

    
    switch (type) {
        case ERROR:  // 致命错误日志
            sprintf(logbuffer, "ERROR: %s:%s Errno=%d exiting pid=%d", s1, s2, errno, getpid());
            break;
        case FORBIDDEN:  // 403响应
            // 发送HTTP 403响应头
            write(socket_fd,
                  "HTTP/1.1 403 Forbidden\nContent-Length: 185\nConnection: "
                  "close\nContent-Type: text/html\n\n<html><head>\n<title>403 "
                  "Forbidden</title>\n</head><body>\n<h1>Forbidden</h1>\nThe requested URL, "
                  "file type or operation is not allowed on this simple static file "
                  "webserver.\n</body></html>\n",
                  271);
            sprintf(logbuffer, "FORBIDDEN: %s:%s", s1, s2);
            break;
        case NOTFOUND:  // 404响应
            write(socket_fd,
                  "HTTP/1.1 404 Not Found\nContent-Length: 136\nConnection: close\nContent-Type: "
                  "text/html\n\n<html><head>\n<title>404 Not Found</title>\n</head><body>\n<h1>Not "
                  "Found</h1>\nThe requested URL was not found on this server.\n</body></html>\n",
                  224);
            sprintf(logbuffer, "NOT FOUND: %s:%s", s1, s2);
            break;
        case LOG:  // 普通日志
            sprintf(logbuffer, " INFO: %s:%s:%d", s1, s2, socket_fd);
            break;
    }

    sprintf(logbuffer, "%s %s", timestamp, logbuffer);

    // 写入日志文件（追加模式）
    if ((fd = open("webserver.log", O_CREAT | O_WRONLY | O_APPEND, 0644)) >= 0) {
        write(fd, logbuffer, strlen(logbuffer));
        write(fd, "\n", 1);
        close(fd);
    }

    // 遇到致命错误时退出进程
    if (type == ERROR || type == NOTFOUND || type == FORBIDDEN) exit(3);
}

/* 处理HTTP请求的核心函数
 * 参数：
 *   fd - 客户端套接字描述符
 *   hit - 请求计数器
 */
/* 日志记录函数（含时间戳） */
void logger(int type, char *s1, char *s2, int socket_fd) {
    FILE *logfp;
    char logbuffer[BUFSIZE * 2];
    time_t now;
    struct tm *tm_info;

    time(&now);
    tm_info = localtime(&now);
    strftime(logbuffer, sizeof(logbuffer), "[%Y-%m-%d %H:%M:%S] ", tm_info);

    switch (type) {
        case ERROR:
            sprintf(logbuffer + strlen(logbuffer), "ERROR: %s:%s Errno=%d exiting pid=%d", s1, s2, errno, getpid());
            break;
        case FORBIDDEN:
            write(socket_fd, "HTTP/1.1 403 Forbidden\nContent-Length: 185\nConnection: close\nContent-Type: text/html\n\n", 95);
            write(socket_fd, "<html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1></body></html>", 85);
            sprintf(logbuffer + strlen(logbuffer), "FORBIDDEN: %s:%s", s1, s2);
            break;
        case NOTFOUND:
            write(socket_fd, "HTTP/1.1 404 Not Found\nContent-Length: 136\nConnection: close\nContent-Type: text/html\n\n", 94);
            write(socket_fd, "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1></body></html>", 83);
            sprintf(logbuffer + strlen(logbuffer), "NOT FOUND: %s:%s", s1, s2);
            break;
        case LOG:
            sprintf(logbuffer + strlen(logbuffer), "INFO: %s:%s:%d", s1, s2, socket_fd);
            break;
    }

    if ((logfp = fopen("webserver.log", "a")) != NULL) {
        fputs(logbuffer, logfp);
        fputs("\n", logfp);
        fclose(logfp);
    }

    if (type == ERROR || type == NOTFOUND || type == FORBIDDEN) exit(3);
}

/* 性能分析计时函数 */
double get_current_time() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
}

/* 请求处理函数 */
void web(int fd, int hit) {
    char request[BUFSIZE], method[16], uri[256], version[16];
    char filename[256], cgiargs[256];
    double start_time, end_time;
    
    start_time = get_current_time();

    /* 读取请求 */
    if (recv(fd, request, BUFSIZE, 0) <= 0) {
        logger(FORBIDDEN, "Failed to read request", "", fd);
    }

    /* 解析请求 */
    sscanf(request, "%s %s %s", method, uri, version);
    if (strcasecmp(method, "GET")) {
        logger(FORBIDDEN, "Method not allowed", method, fd);
    }

    /* 处理路径遍历攻击 */
    if (strstr(uri, "..")) {
        logger(FORBIDDEN, "Path traversal detected", uri, fd);
    }

    /* 构建本地文件路径 */
    sprintf(filename, "./%s", uri);
    if (uri[strlen(uri)-1] == '/') {
        strcat(filename, "index.html");
    }

    /* 获取文件状态 */
    struct stat st;
    if (stat(filename, &st) == -1) {
        logger(NOTFOUND, filename, "", fd);
    }

    /* 发送HTTP响应头 */
    char header[BUFSIZE];
    sprintf(header, "HTTP/1.1 200 OK\r\nServer: nweb/%d\r\nContent-Length: %ld\r\nConnection: close\r\n\r\n", VERSION, st.st_size);
    send(fd, header, strlen(header), 0);

    /* 发送文件内容 */
    FILE *fp = fopen(filename, "rb");
    if (!fp) {
        logger(ERROR, "Failed to open file", filename, fd);
    }
    char buffer[BUFSIZE];
    size_t bytes_sent;
    while ((bytes_sent = fread(buffer, 1, BUFSIZE, fp)) > 0) {
        send(fd, buffer, bytes_sent, 0);
    }
    fclose(fp);

    end_time = get_current_time();
    printf("Request handled in %.3f ms\n", (end_time - start_time) * 1000);
}

/* 主函数 */
int main(int argc, char *argv[]) {
    int server_port, server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);

    if (argc != 3) {
        fprintf(stderr, "Usage: %s <port> <root_dir>\n", argv[0]);
        exit(1);
    }

    server_port = atoi(argv[1]);
    if (server_port < 1 || server_port > 65535) {
        fprintf(stderr, "Invalid port number\n");
        exit(1);
    }

    /* 创建服务器套接字 */
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket creation failed");
        exit(1);
    }

    /* 设置地址重用 */
    int optval = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    /* 绑定地址 */
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(server_port);
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        exit(1);
    }

    /* 监听连接 */
    if (listen(server_fd, 10) < 0) {
        perror("listen failed");
        exit(1);
    }

    printf("Server running on port %d...\n", server_port);

    while (1) {
        /* 接受客户端连接 */
        client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
        if (client_fd < 0) {
            perror("accept failed");
            continue;
        }

        /* 处理请求（子进程处理） */
        if (fork() == 0) {
            close(server_fd);
            web(client_fd, 1);
            close(client_fd);
            exit(0);
        }
        close(client_fd);
    }

    return 0;
}